package hit.core;

import java.util.Calendar;
import java.util.Date;
import hit.reports.Visitor;

/**
 * Represents an Item in the HIT system.
 * */
@SuppressWarnings("serial")
public class Item extends HITBase {
	private Product product;
	private int productID;
	private Barcode barcode;
	private Date entry;
	private Date exit;
	//private Date expiration;
	private ProductContainer container;
	private int containerID;
	
	/**
	 * The constructor is protected because it should only be used by the {@link Product} class.
	 * To instantiate an Item, use the {@link Product#createItem() Product.createItem()} method.
	 * */
	public Item() {
		assert true;
		
		product = null;
		barcode = null;
		entry = new Date();
		exit = null;
		//expiration = null;
		container = null;
	}
	
	/**
	 * @return whether the Item has already been removed and used.
	 * */
	public boolean IsRemoved() {
		if (exit != null)
			return true;
		return false;
	}
	
	public void Remove() {
		Remove(new Date());
	}
	
	/**
	 * Sets the removal time of the Item
	 * @throws IllegalStateException thrown if the Item has previously been removed
	 * */
	public void Remove(Date newExit) {
		if (exit != null)
			throw new IllegalStateException("This Item was already removed");
		if (newExit.before(this.entry))
			throw new IllegalArgumentException("Invalid exit date.");
		exit = (Date)newExit.clone();
	}

	/**
	 * @return the {@link Product} that this Item is an instance of
	 * */
	public Product GetProduct() {
		return product;
	}
	
	/**
	 * Sets the {@link Product} that this Item is an instance of. This should only
	 * be used by the Product class and the DatabaseDAO
	 * @param product the new Product
	 * */
	public void SetProduct(Product product) {
		assert product != null;
		
		this.product = product;
	}
	
	/**
	 * Return the id of the Product that this Item belongs to
	 * @return the id
	 * */
	public int GetProductID() {
		if (product != null) {
			return product.getId();
		}
		return productID;
	}
	
	/**
	 * Set the id of the Product that this Item belongs to
	 * @param productID the id of the Product
	 * */
	public void SetProductID(int productID) {
		this.productID = productID;
	}
	
	/**
	 * @return the {@link Barcode} of this Item
	 * */
	public Barcode GetBarcode() {
		return barcode;
	}
	
	/**
	 * Sets the {@link Barcode} that identifies this Item. This should only be used
	 * by the {@link Product} class and the ItemDatabaseDAO
	 * @param barcode the new Barcode
	 * @throws IllegalArgumentException
	 * */
	public void SetBarcode(Barcode barcode) throws IllegalArgumentException {
		if (!Barcode.IsValid(barcode) || !barcode.IsLocal())
			throw new IllegalArgumentException("Invalid Item Barcode");
		
		this.barcode = barcode;
	}
	
	/**
	 * Returns the Date that the Item was added to the system
	 * */
	public Date GetEntry() {
		return entry;
	}
	
	/**
	 * @param entry the new Entry date for the Item
	 * @throws IllegalArgumentException thrown if the date is in the future
	 * */
	public void SetEntry(Date entry) {
		if (entry.after(new Date()))
			throw new IllegalArgumentException("The entry date can't be in the future");
		this.entry = entry;
	}

	/**
	 * Returns the Date that the Item exited the system, or null
	 * */
	public Date GetExit() {
		return exit;
	}

	/**
	 * @param exit the new Exit date for the Item
	 * @throws IllegalArgumentException thrown if the date is in the future
	 * */
	public void SetExit(Date exit) throws IllegalArgumentException {
		if (exit != null && exit.after(new Date()))
			throw new IllegalArgumentException("The exit date can't be in the future");
		this.exit = exit;
	}

	/**
	 * @return the date that the Item expires
	 * */
	public Date GetExpiration() {
		
		if (this.product.GetShelfLife() > 0)
		{
			Calendar calendar = Calendar.getInstance();
			calendar.setTime(this.entry);
			calendar.add(Calendar.MONTH, this.product.GetShelfLife());

			return calendar.getTime();
		}
		else
		{
			return null;
		}
	}
	
	/**
	 * @param expiration the new Expiration date for the Item
	 * */
	/*public void SetExpiration(Date expiration) {
		this.expiration = expiration;
	}*/
	
	/**
	 * @return the {@link ProductContainer} that contains this Item
	 * */
	public ProductContainer GetContainer() {
		return container;
	}
	
	/**
	 * Return the id of the ProductContainer this Item belongs to
	 * @return the id of the ProductContainer
	 * */
	public int GetContainerID() {
		if (container != null) {
			return container.getId();
		}
		return containerID;
	}
	
	/**
	 * Set the id of the ProductContainer this Item belongs to
	 * @param containerID the id of the ProductContainer
	 * */
	public void SetContainerID(int containerID) {
		this.containerID = containerID;
	}
	
	public StorageUnit GetRootStorageUnit() {
		
		return container.GetRootContainer();
	}

	/**
	 * @param container the new {@link ProductContainer} that will contain the Item
	 * @throws IllegalArgumentException thrown if container is null
	 * */
	public void SetContainer(ProductContainer container) {
		if (container == null)
			throw new IllegalArgumentException("ProductContainer cannot be null");
		this.container = container;
	}
	
	/**
	 * Determines if an Item is valid
	 * @param item the Item to check
	 * @return whether the Item is valid
	 * */
	public static boolean IsValid(Item item) {
		if (item == null)
			return false;
		
		boolean isValid = true;
		if (!Barcode.IsValid(item.GetBarcode()) || !item.GetBarcode().IsLocal())
			isValid = false;
		if (item.GetContainer() == null)
			isValid = false;
		if (item.GetEntry().after(new Date()))
			isValid = false;
		if (item.GetExit() != null && item.GetExit().after(new Date()))
			isValid = false;
		if (item.GetProduct() == null)
			isValid = false;
		
		return isValid;
	}
	
	public void Accept(Visitor visitor){
		visitor.VisitItem(this);
	}

	
	@Override
	public boolean equals(Object o) {
		if (!(o instanceof Item))
			return false;
		
		Item other = (Item) o;
		return this.barcode.equals(other.GetBarcode());
	}
	
	@Override
	public int hashCode() {
		return this.barcode.hashCode();
	}
}
